#include "display_typing_hw.h"

#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include "hi_errno.h"
#include "hi_io.h"
#include "hi_spi.h"
#include "iot_gpio.h"
#include "iot_errno.h"

#define DISPLAY_TYPING_SPI_ID           HI_SPI_ID_0
#define DISPLAY_TYPING_SPI_FREQ         2000000U
#define DISPLAY_TYPING_SPI_CPOL         HI_SPI_CFG_CLOCK_CPOL_0
#define DISPLAY_TYPING_SPI_CPHA         HI_SPI_CFG_CLOCK_CPHA_0
#define DISPLAY_TYPING_SPI_DATA_WIDTH   HI_SPI_CFG_DATA_WIDTH_E_8BIT
#define DISPLAY_TYPING_SPI_CHUNK        64U
/* HiSpark Pegasus default wiring: CS=GPIO12, CLK=GPIO10, MOSI=GPIO9, MISO=GPIO7, DC=GPIO11, RST=GPIO8 */
#define DISPLAY_TYPING_PIN_CS           HI_IO_NAME_GPIO_12
#define DISPLAY_TYPING_PIN_SCK          HI_IO_NAME_GPIO_10
#define DISPLAY_TYPING_PIN_MOSI         HI_IO_NAME_GPIO_9
#define DISPLAY_TYPING_PIN_MISO         HI_IO_NAME_GPIO_7
#define DISPLAY_TYPING_PIN_DC           HI_IO_NAME_GPIO_11
#define DISPLAY_TYPING_PIN_RST          HI_IO_NAME_GPIO_8

#define DISPLAY_TYPING_DC_GPIO          ((unsigned int)DISPLAY_TYPING_PIN_DC)
#define DISPLAY_TYPING_RST_GPIO         ((unsigned int)DISPLAY_TYPING_PIN_RST)

static bool g_busInitialized = false;

static void DisplayTypingDemo_ResetPanel(void);
static void DisplayTypingDemo_SendInitSequence(void);
static bool DisplayTypingDemo_ConfigurePins(void);
static bool DisplayTypingDemo_SpiWrite(const uint8_t *data, uint32_t length, bool isData);
static void DisplayTypingDemo_SpiSelfTest(void);

void DisplayTypingDemo_SpiInit(void)
{
    if (g_busInitialized) {
        return;
    }

    if (!DisplayTypingDemo_ConfigurePins()) {
        return;
    }

    hi_spi_cfg_basic_info cfg = {0};
    cfg.cpol = DISPLAY_TYPING_SPI_CPOL;
    cfg.cpha = DISPLAY_TYPING_SPI_CPHA;
    cfg.fram_mode = HI_SPI_CFG_FRAM_MODE_MOTOROLA;
    cfg.data_width = DISPLAY_TYPING_SPI_DATA_WIDTH;
    cfg.endian = HI_SPI_CFG_ENDIAN_LITTLE;
    cfg.freq = DISPLAY_TYPING_SPI_FREQ;

    hi_spi_cfg_init_param initParam = {0};
    initParam.is_slave = HI_FALSE;

    if (hi_spi_init(DISPLAY_TYPING_SPI_ID, initParam, &cfg) != HI_ERR_SUCCESS) {
        printf("[display_typing_demo] hi_spi_init failed\n");
        return;
    }

    hi_spi_set_irq_mode(DISPLAY_TYPING_SPI_ID, HI_FALSE);
    hi_spi_set_dma_mode(DISPLAY_TYPING_SPI_ID, HI_FALSE);

    g_busInitialized = true;
}

void DisplayTypingDemo_Ssd1306Init(void)
{
    DisplayTypingDemo_SpiInit();
    if (!g_busInitialized) {
        return;
    }

    DisplayTypingDemo_ResetPanel();
    DisplayTypingDemo_SendInitSequence();
    DisplayTypingDemo_SpiSelfTest();
}

void DisplayTypingDemo_Ssd1306WriteCommand(uint8_t cmd)
{
    if (!g_busInitialized) {
        return;
    }
    DisplayTypingDemo_SpiWrite(&cmd, 1, false);
}

void DisplayTypingDemo_Ssd1306WriteData(const uint8_t *data, uint32_t length)
{
    if ((!g_busInitialized) || (data == NULL) || (length == 0)) {
        return;
    }

    DisplayTypingDemo_SpiWrite(data, length, true);
}

static void DisplayTypingDemo_Ssd1306SetPageColumn(uint8_t page, uint8_t column)
{
    /* SH1106-style addressing, column offset = 2 */
    const uint8_t effective = column + 2;
    DisplayTypingDemo_Ssd1306WriteCommand(0xB0 + page);
    DisplayTypingDemo_Ssd1306WriteCommand(0x10 | (effective >> 4));
    DisplayTypingDemo_Ssd1306WriteCommand(effective & 0x0F);
}

void DisplayTypingDemo_Ssd1306SetColumnAndPage(uint8_t colStart, uint8_t colEnd,
                                               uint8_t pageStart, uint8_t pageEnd)
{
    (void)colEnd;
    (void)pageEnd;
    DisplayTypingDemo_Ssd1306SetPageColumn(pageStart, colStart);
}

void DisplayTypingDemo_Ssd1306Fill(uint8_t value)
{
    if (!g_busInitialized) {
        return;
    }

    uint8_t scanline[DISPLAY_TYPING_SCREEN_WIDTH];
    memset(scanline, value, sizeof(scanline));

    for (uint8_t page = 0; page < DISPLAY_TYPING_SCREEN_PAGES; ++page) {
        DisplayTypingDemo_Ssd1306SetPageColumn(page, 0);
        if (!DisplayTypingDemo_SpiWrite(scanline, DISPLAY_TYPING_SCREEN_WIDTH, true)) {
            return;
        }
    }
}

static void DisplayTypingDemo_ResetPanel(void)
{
    IoTGpioSetDir(DISPLAY_TYPING_RST_GPIO, IOT_GPIO_DIR_OUT);
    IoTGpioSetOutputVal(DISPLAY_TYPING_RST_GPIO, IOT_GPIO_VALUE1);
    usleep(2000);
    IoTGpioSetOutputVal(DISPLAY_TYPING_RST_GPIO, IOT_GPIO_VALUE0);
    usleep(20000);
    IoTGpioSetOutputVal(DISPLAY_TYPING_RST_GPIO, IOT_GPIO_VALUE1);
    usleep(20000);
}

static void DisplayTypingDemo_SendInitSequence(void)
{
    static const uint8_t initCmds[] = {
        0xAE, 0x00, 0x10, 0x40,
        0xB0, 0x81, 0xCF, 0xA1,
        0xA6, 0xA8, 0x3F, 0xC8,
        0xD3, 0x00, 0xD5, 0x80,
        0xD8, 0x05, 0xD9, 0xF1,
        0xDA, 0x12, 0xDB, 0x30,
        0x8D, 0x14, 0xAF
    };

    for (uint32_t i = 0; i < sizeof(initCmds); ++i) {
        DisplayTypingDemo_Ssd1306WriteCommand(initCmds[i]);
    }
}

static bool DisplayTypingDemo_ConfigurePins(void)
{
    if (hi_io_set_func(DISPLAY_TYPING_PIN_CS, HI_IO_FUNC_GPIO_12_SPI0_CSN) != HI_ERR_SUCCESS) {
        printf("[display_typing_demo] hi_io_set_func CS failed\n");
        return false;
    }
    if (hi_io_set_func(DISPLAY_TYPING_PIN_SCK, HI_IO_FUNC_GPIO_10_SPI0_CK) != HI_ERR_SUCCESS) {
        printf("[display_typing_demo] hi_io_set_func SCK failed\n");
        return false;
    }
    if (hi_io_set_func(DISPLAY_TYPING_PIN_MOSI, HI_IO_FUNC_GPIO_9_SPI0_TXD) != HI_ERR_SUCCESS) {
        printf("[display_typing_demo] hi_io_set_func MOSI failed\n");
        return false;
    }
    if (hi_io_set_func(DISPLAY_TYPING_PIN_MISO, HI_IO_FUNC_GPIO_7_SPI0_RXD) != HI_ERR_SUCCESS) {
        printf("[display_typing_demo] hi_io_set_func MISO failed\n");
        return false;
    }

    if (IoTGpioInit(DISPLAY_TYPING_DC_GPIO) != IOT_SUCCESS) {
        printf("[display_typing_demo] IoTGpioInit failed on DC\n");
        return false;
    }
    if (IoTGpioInit(DISPLAY_TYPING_RST_GPIO) != IOT_SUCCESS) {
        printf("[display_typing_demo] IoTGpioInit failed on RST\n");
        return false;
    }

    if (hi_io_set_func(DISPLAY_TYPING_PIN_DC, HI_IO_FUNC_GPIO_11_GPIO) != HI_ERR_SUCCESS) {
        printf("[display_typing_demo] hi_io_set_func DC failed\n");
        return false;
    }
    if (hi_io_set_func(DISPLAY_TYPING_PIN_RST, HI_IO_FUNC_GPIO_8_GPIO) != HI_ERR_SUCCESS) {
        printf("[display_typing_demo] hi_io_set_func RST failed\n");
        return false;
    }

    IoTGpioSetDir(DISPLAY_TYPING_DC_GPIO, IOT_GPIO_DIR_OUT);
    IoTGpioSetOutputVal(DISPLAY_TYPING_DC_GPIO, IOT_GPIO_VALUE1);
    IoTGpioSetDir(DISPLAY_TYPING_RST_GPIO, IOT_GPIO_DIR_OUT);
    IoTGpioSetOutputVal(DISPLAY_TYPING_RST_GPIO, IOT_GPIO_VALUE1);

    hi_io_set_driver_strength(DISPLAY_TYPING_PIN_SCK, HI_IO_DRIVER_STRENGTH_2);
    hi_io_set_driver_strength(DISPLAY_TYPING_PIN_MOSI, HI_IO_DRIVER_STRENGTH_2);

    return true;
}

static bool DisplayTypingDemo_SpiWrite(const uint8_t *data, uint32_t length, bool isData)
{
    if ((data == NULL) || (length == 0)) {
        return false;
    }

    IoTGpioSetOutputVal(DISPLAY_TYPING_DC_GPIO, isData ? IOT_GPIO_VALUE1 : IOT_GPIO_VALUE0);

    const uint8_t *cursor = data;
    uint32_t remaining = length;
    while (remaining > 0) {
        uint32_t chunk = (remaining > DISPLAY_TYPING_SPI_CHUNK) ? DISPLAY_TYPING_SPI_CHUNK : remaining;
        hi_u32 status = hi_spi_host_write(DISPLAY_TYPING_SPI_ID, (hi_pvoid)cursor, chunk);
        if (status != HI_ERR_SUCCESS) {
            printf("[display_typing_demo] hi_spi_host_write failed: 0x%x\n", status);
            return false;
        }
        cursor += chunk;
        remaining -= chunk;
    }

    return true;
}

bool DisplayTypingDemo_IsInitialized(void)
{
    return g_busInitialized;
}

void DisplayTypingDemo_Ssd1306FlushFull(const uint8_t *buffer, uint32_t length)
{
    if ((!g_busInitialized) || (buffer == NULL)) {
        return;
    }

    /* Expecting 128*64/8 = 1024 bytes */
    const uint32_t pageSize = DISPLAY_TYPING_SCREEN_WIDTH;
    uint32_t offset = 0;

    for (uint8_t page = 0; page < DISPLAY_TYPING_SCREEN_PAGES; ++page) {
        if (offset + pageSize > length) {
            break;
        }
        DisplayTypingDemo_Ssd1306SetPageColumn(page, 0);
        DisplayTypingDemo_Ssd1306WriteData(buffer + offset, DISPLAY_TYPING_SCREEN_WIDTH);
        offset += pageSize;
    }
}

static void DisplayTypingDemo_SpiSelfTest(void)
{
    printf("[display_typing_demo] SPI self-test start\n");
    printf("[display_typing_demo] CS=%d SCK=%d MOSI=%d MISO=%d DC=%d RST=%d\n",
           DISPLAY_TYPING_PIN_CS, DISPLAY_TYPING_PIN_SCK, DISPLAY_TYPING_PIN_MOSI,
           DISPLAY_TYPING_PIN_MISO, DISPLAY_TYPING_PIN_DC, DISPLAY_TYPING_PIN_RST);

    uint8_t cmd[] = { 0xAE, 0xAF };
    if (!DisplayTypingDemo_SpiWrite(cmd, sizeof(cmd), false)) {
        printf("[display_typing_demo] SPI command write failed\n");
    } else {
        printf("[display_typing_demo] SPI command write ok\n");
    }

    uint8_t pattern[DISPLAY_TYPING_SCREEN_WIDTH];
    for (uint32_t i = 0; i < DISPLAY_TYPING_SCREEN_WIDTH; ++i) {
        pattern[i] = (i & 0x01) ? 0xFF : 0x00;
    }
    DisplayTypingDemo_Ssd1306SetColumnAndPage(0, DISPLAY_TYPING_SCREEN_WIDTH - 1, 0, 0);
    if (!DisplayTypingDemo_SpiWrite(pattern, sizeof(pattern), true)) {
        printf("[display_typing_demo] SPI data write failed\n");
    } else {
        printf("[display_typing_demo] SPI data write ok\n");
    }
}
